// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// copied from Dart repo

#library('http_util');

#import('dart:io');

class HttpHeadersImpl implements HttpHeaders {
  HttpHeadersImpl() : _headers = new Map<String, List<String>>();
  HttpHeadersImpl.fromMap(this._headers);

  Map<String, List<String>> get map() => _headers;
  
  List<String> operator[](String name) {
    name = name.toLowerCase();
    return _headers[name];
  }

  String value(String name) {
    name = name.toLowerCase();
    List<String> values = _headers[name];
    if (values == null) return null;
    if (values.length > 1) {
      throw new HttpException("More than one value for header $name");
    }
    return values[0];
  }

  void add(String name, Object value) {
    _checkMutable();
    if (value is List) {
      for (int i = 0; i < value.length; i++) {
        _add(name, value[i]);
      }
    } else {
      _add(name, value);
    }
  }

  void set(String name, Object value) {
    name = name.toLowerCase();
    _checkMutable();
    removeAll(name);
    add(name, value);
  }

  void remove(String name, Object value) {
    _checkMutable();
    name = name.toLowerCase();
    List<String> values = _headers[name];
    if (values != null) {
      int index = values.indexOf(value);
      if (index != -1) {
        values.removeRange(index, 1);
      }
    }
  }

  void removeAll(String name) {
    _checkMutable();
    name = name.toLowerCase();
    _headers.remove(name);
  }

  void forEach(void f(String name, List<String> values)) {
    _headers.forEach(f);
  }

  void noFolding(String name) {
    if (_noFoldingHeaders == null) _noFoldingHeaders = new List<String>();
    _noFoldingHeaders.add(name);
  }

  String get host() => _host;

  void set host(String host) {
    _checkMutable();
    _host = host;
    _updateHostHeader();
  }

  int get port() => _port;

  void set port(int port) {
    _checkMutable();
    _port = port;
    _updateHostHeader();
  }

  Date get date() {
    List<String> values = _headers["date"];
    if (values != null) {
      try {
        return _HttpUtils.parseDate(values[0]);
      } catch (Exception e) {
        return null;
      }
    }
    return null;
  }

  void set date(Date date) {
    _checkMutable();
    // Format "Date" header with date in Greenwich Mean Time (GMT).
    String formatted = _HttpUtils.formatDate(expires.toUtc());
    _set("date", formatted);
  }

  Date get expires() {
    List<String> values = _headers["expires"];
    if (values != null) {
      try {
        return _HttpUtils.parseDate(values[0]);
      } catch (Exception e) {
        return null;
      }
    }
    return null;
  }

  void set expires(Date expires) {
    _checkMutable();
    // Format "Expires" header with date in Greenwich Mean Time (GMT).
    String formatted = _HttpUtils.formatDate(expires.toUtc());
    _set("expires", formatted);
  }

  ContentType get contentType() {
    var values = _headers["content-type"];
    if (values != null) {
      return new ContentType.fromString(values[0]);
    } else {
      return new ContentType();
    }
  }

  void set contentType(ContentType contentType) {
    _checkMutable();
    _set("content-type", contentType.toString());
  }

  void _add(String name, Object value) {
    // TODO(sgjesse): Add immutable state throw HttpException is immutable.
    if (name.toLowerCase() == "date") {
      if (value is Date) {
        date = value;
      } else if (value is String) {
        _set("date", value);
      } else {
        throw new HttpException("Unexpected type for header named $name");
      }
    } else if (name.toLowerCase() == "expires") {
      if (value is Date) {
        expires = value;
      } else if (value is String) {
        _set("expires", value);
      } else {
        throw new HttpException("Unexpected type for header named $name");
      }
    } else if (name.toLowerCase() == "host") {
      int pos = value.indexOf(":");
      if (pos == -1) {
        _host = value;
        _port = HttpClient.DEFAULT_HTTP_PORT;
      } else {
        if (pos > 0) {
          _host = value.substring(0, pos);
        } else {
          _host = null;
        }
        if (pos + 1 == value.length) {
          _port = HttpClient.DEFAULT_HTTP_PORT;
        } else {
          try {
            _port = Math.parseInt(value.substring(pos + 1));
          } catch (BadNumberFormatException e) {
            _port = null;
          }
        }
        _set("host", value);
      }
    } else if (name.toLowerCase() == "content-type") {
      _set("content-type", value);
    } else {
      name = name.toLowerCase();
      List<String> values = _headers[name];
      if (values == null) {
        values = new List<String>();
        _headers[name] = values;
      }
      values.add(value.toString());
    }
  }

  void _set(String name, String value) {
    name = name.toLowerCase();
    List<String> values = new List<String>();
    _headers[name] = values;
    values.add(value);
  }

  _checkMutable() {
    if (!_mutable) throw new HttpException("HTTP headers are not mutable");
  }

  _updateHostHeader() {
    bool defaultPort = _port == null || _port == HttpClient.DEFAULT_HTTP_PORT;
    String portPart = defaultPort ? "" : ":$_port";
    _set("host", "$host$portPart");
  }

  _foldHeader(String name) {
    if (name == "set-cookie" ||
        (_noFoldingHeaders != null &&
         _noFoldingHeaders.indexOf(name) != -1)) {
      return false;
    }
    return true;
  }

  _write(_HttpConnectionBase connection) {
    final COLONSP = const [_CharCode.COLON, _CharCode.SP];
    final COMMASP = const [_CharCode.COMMA, _CharCode.SP];
    final CRLF = const [_CharCode.CR, _CharCode.LF];

    // Format headers.
    _headers.forEach((String name, List<String> values) {
      bool fold = _foldHeader(name);
      List<int> data;
      data = name.charCodes();
      connection._write(data);
      connection._write(COLONSP);
      for (int i = 0; i < values.length; i++) {
        if (i > 0) {
          if (fold) {
            connection._write(COMMASP);
          } else {
            connection._write(CRLF);
            data = name.charCodes();
            connection._write(data);
            connection._write(COLONSP);
          }
        }
        data = values[i].charCodes();
        connection._write(data);
      }
      connection._write(CRLF);
    });
  }

  String toString() {
    StringBuffer sb = new StringBuffer();
    _headers.forEach((String name, List<String> values) {
      sb.add(name);
      sb.add(": ");
      bool fold = _foldHeader(name);
      for (int i = 0; i < values.length; i++) {
        if (i > 0) {
          if (fold) {
            sb.add(", ");
          } else {
            sb.add("\n");
            sb.add(name);
            sb.add(": ");
          }
        }
        sb.add(values[i]);
      }
      sb.add("\n");
    });
    return sb.toString();
  }

  bool _mutable = true;  // Are the headers currently mutable?
  Map<String, List<String>> _headers;
  List<String> _noFoldingHeaders;

  String _host;
  int _port;
}

